Data mining Final Project
bus_stop
-
- 실제로 외출을 할 때 대부분 버스로 이동하며 주변에 학교에서 이용하는 학생이나 여러 아파트 단지에서 이용하는 주민들이 많지만 배차 간격, 환승 등의 불편함을 겪고 대중교통에서 오는 불편함이 큰 것을 알기 때문에 실제 경험을 바탕으로 하여 분석을 진행하기 위해 선정하였다.
from google.colab import drive
drive.mount('/content/drive') #구글드라이브와 코랩 연결
pip install geopandas # 코랩 geopandas 설치
pip install pydeck # 코랩 pydeck 설치
import pandas as pd
import numpy as np
import geopandas as gpd
import pydeck
import folium
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
#한글깨짐방지 폰트 설정
plt.rc('font', family='NanumGothic')
from matplotlib import rc
import matplotlib.font_manager as fm
fm._rebuild()
fm.get_fontconfig_fonts()
font_location = '/content/drive/MyDrive/NanumGothic.ttf' # 폰트 파일 이름, 디렉토리
font_name = fm.FontProperties(fname=font_location).get_name()
matplotlib.rc('font', family=font_name)
from matplotlib import font_manager
import matplotlib
matplotlib.font_manager._rebuild()
for i in font_manager.fontManager.ttflist:
if 'Nanum' in i.name:
print(i.name, i.fname)
- TripChain
- 버스 탑승 정보, 승하차역 정보, 환승 정보
TripChain = pd.read_csv('/content/drive/MyDrive/TripChain_re.csv')
TripChain.head()
- station_table
- 정류소 ID, 위치, 위도, 경도
station_table = pd.read_csv('/content/drive/MyDrive/stations_table.csv')
station_table
- emd_scco_geo
- 화성시 읍면동 geometry, 이름, 코드
emd_scco_geo =gpd.read_file(open('/content/drive/MyDrive/tl_scco_emd.geojson', encoding='utf-8'))
emd_scco_geo
- cell_pop
- 인구밀도, geometry
cell_pop = gpd.read_file(open('/content/drive/MyDrive/h_100m_cell_pop.geojson', encoding='utf-8'))
cell_pop
- moc_link
- link ID, node, road name, geometry
moc_link = gpd.read_file(open('/content/drive/MyDrive/moc_link_2018.geojson', encoding='utf-8'))
moc_link
route_mapping = pd.read_csv('/content/drive/MyDrive/routestationmapping.csv')
route_mapping
- route_info
- 버스정류장 이름, 노선 ID, 정류장 ID, 모바일 정류장 ID
route_info = pd.read_csv('/content/drive/MyDrive/routestationinfo.csv')
route_info.head()
Trip_new = pd.merge(TripChain,route_mapping, how='left', left_on = "'||버스노선ID1||'", right_on = '표준노선ID').drop(["'||사용자구분||'", "'||총통행거리||'", "'||총탑승시간||'","'||총소요시간||'"], axis = 1)
# TripChain,route_mapping 데이터셋을 합친 후 사용하지 않는 열 제거
- station_table 전처리
station_table = station_table.dropna() # na값 제거
station_table = station_table[station_table['시군명'] == '화성시'] # 시군명이 화성시인 행을 제외하고 모두 삭제
station_table['emd'] = station_table['위치'].str[7:11] # 주소정보에서 읍면동만 추출
station_table = station_table[~station_table['emd'].str.contains('구')] # 화성시는 읍면동 데이터만 있는데 위치는 수원시이지만 관할구청이 화성시이기 때문에 포함되어있던 행 제거
station_table['emd'] = station_table['emd'].str.strip() # emd 열의 공백 제거
- cell_pop 전처리
unique 함수를 써보면 전체 데이터에서 화성시에 해당하는 읍면동 코드만 있는 것을 볼 수 있다.
cell_pop['emd_cd'].unique()
화성시가 아닌 데이터가 na로 되어있기 때문에 모두 삭제한다.
cell_pop = cell_pop.dropna() # 화성시가 아닌 행 즉, None값 제거
읍면동 코드별로 그룹화
grouped = cell_pop['pop_ttl'].groupby(cell_pop['emd_cd']) #emd_cd 별로 pop_ttl 그룹화
grouped = pd.DataFrame(grouped.sum()) # 그룹화 된 값을 더해서 읍면동별 인구수 데이터프레임 생성
grouped['index'] = grouped.index # 인덱스값으로 인덱스 지정
new_emd = pd.merge(emd_scco_geo, grouped, how='inner', on='emd_cd') # emd_scco_geo, grouped 합치기
new_emd = new_emd.drop(['index'], axis = 1) # index 값 제거
new_emd_sort = new_emd.sort_values(by = 'pop_ttl', ascending = False) # pop_ttl 을 기준으로 내림차순 정렬
시각화할 때 y축의 스케일 차이를 줄이기 위해 100을 기준으로 인구 상위권과 하위권 구분
top_emd = new_emd_sort.head(14)
tail_emd = new_emd_sort.tail(23)
- Tripchain 전처리
사용하지 않는 컬럼 제거
TripChain = TripChain.fillna(0) #데이터 처리를 위해 모든 na값을 0으로 대체
TripChain[["'||버스노선ID1||'","'||버스노선ID2||'","'||버스노선ID3||'","'||버스노선ID4||'","'||버스노선ID5||'", "'||차량ID1||'", "'||차량ID2||'", "'||차량ID3||'", "'||차량ID4||'", "'||차량ID5||'","'||승차역ID1||'","'||승차역ID2||'","'||승차역ID3||'","'||하차역ID4||'","'||하차역ID5||'", "'||최종하차역ID||'"]] = TripChain[["'||버스노선ID1||'","'||버스노선ID2||'","'||버스노선ID3||'","'||버스노선ID4||'","'||버스노선ID5||'", "'||차량ID1||'", "'||차량ID2||'", "'||차량ID3||'", "'||차량ID4||'", "'||차량ID5||'","'||승차역ID1||'","'||승차역ID2||'","'||승차역ID3||'","'||하차역ID4||'","'||하차역ID5||'","'||최종하차역ID||'"]].astype(int)
# 메모리가 소모가 크기 때문에 float 인 데이터를 int로 변환
TripChain.drop(["'||사용자구분||'", "'||총탑승시간||'", "'||총소요시간||'"], axis = 1)
- route_mapping 전처리
route_mapping = route_mapping.drop(["운수사명","운수사ID", "구분"], axis = 1) # 사용하지 않는 열 제거
- 출근 시간 시각화 자료
Trip_new["'||최초승차일시||'"] = Trip_new["'||최초승차일시||'"].astype(str) # 시간으로 사용하기 위해
Trip_new["start_time"] = pd.to_datetime(Trip_new["'||최초승차일시||'"], infer_datetime_format = True) # 시간 형식으로 변환
Trip_new["time_hour"] = Trip_new["start_time"].dt.hour # 시간 추출해서 time_hour 에 저장
time_table = pd.DataFrame(Trip_new.groupby("time_hour")["암호화카드번호||'"].count()) # 시간대별 사용자 수 데이터프레임 생성
time_table['time_hour'] = time_table.index # 인덱스 번호(시간)로 열 생성
time_table # 시간대별 이용자 수
plt.figure(figsize = (18,4))
sns.barplot(x="time_hour", y="암호화카드번호||'", data=time_table)
plt.xlabel("시간(0~23시)", fontsize = 20)
plt.ylabel("이용자수(명)", fontsize = 20)
- 위의 코드 실행 시 한글 글꼴이 적용되지 않아 첨부한 원본 그래프 사진
- 출근시간인 7-8시, 퇴근시간인 17-18시에 이용자 수가 가장 많은 것을 볼 수 있다.
condition = (Trip_new["time_hour"]== 7) | (Trip_new["time_hour"]== 8) # 출퇴근 시간인 7,8시로 조건식 작성
df_work = Trip_new[condition] # 데이터셋에서 이용 시간이 7,8시인 행 추출
df_work.groupby("노선명").count() # 버스 노선 별 이용 횟수
t = pd.DataFrame(Trip_new.groupby("노선명")["암호화카드번호||'"].count()) # 노선명 별 이용 횟수
t.sort_values(by = "암호화카드번호||'", ascending= False).head(10) # 카드번호의 카운트 횟수를 기준으로 내림차순 정렬
- 이용자 수가 가장 많은 92-1번 버스는 동탄에서 수원을 잇는 버스 노선이다.
⦁ 2018년 7월 승차일 기준 1~4일의 버스 카드태깅 정보을 담은 TripChain 데이터셋 - 사람마다 환승을 하는 횟수가 다르기 때문에 환승에서 파생된 정보에 NA값 발생한다.
⦁ 17~18년 기준, 경기도 버스 정류장에 대한 정보를 담은 stations_table 데이터셋 - 경기도 내에 있는 버스정류장 정보를 담았지만 대회에서는 화성시에 대한 정보만 제공했기 때문에 화성시가 아닌 경우 NA값 발생한다.
-> 가장 큰 두개의 데이터셋에서 NA가 대량 발생하였고 이 외에는 큰 결측치나 이상치는 없는 것을 확인
- 1차 시각화
plt.rcParams['font.size'] = 25 # 글씨 크기 설정
plt.figure(figsize = (45,10)) # 그림 크기 설정
sns.barplot(x="emd_kor_nm", y="pop_ttl", data=new_emd_sort) # x축, y축 설정 후 그래프 그리기
plt.xlabel("읍면동") # x축 이름
plt.ylabel("인구수(명)") # y축 이름
plt.xticks(rotation=45) # x축에 들어가는 데이터가 많기 때문에 가독성을 위해 x축 글씨 설정
전체적으로 봤을 때는 y축의 스케일 차이가 많이 나서 구분하기 쉽게 보기 위해 상위권과 하위권을 구분지어 시각화
plt.figure(figsize = (18,4)) # 그림 크기 설정
sns.barplot(x="emd_kor_nm", y="pop_ttl", data=top_emd) # x축, y축 설정 후 그래프 그리기
plt.xlabel("읍면동") # x축 이름
plt.ylabel("인구수") # y축 이름
plt.figure(figsize = (30,6)) # 그림 크기 설정
sns.barplot(x="emd_kor_nm", y="pop_ttl", data=tail_emd) # x축, y축 설정 후 그래프 그리기
plt.xlabel("읍면동") # x축 이름
plt.ylabel("인구수") # y축 이름
- cell_pop
- top_emd = 향남읍(41590259), 봉담읍(41590253), 반송동(41590127), 청계동(41590130), 병정동(41590117)
- tail_emd = 신동(41590133), 양감면(41590400), 서신면(41590350), 중동(41590132), 마도면(41590330)
pop_test = cell_pop.groupby(['emd_cd']).sum().drop(['id'], axis = 1) # 사용하지 않는 id를 제거한 읍면동별 인구수
pop_test
pop_total = pop_test.iloc[:, 0:21].drop(['pop_mn_ttl'], axis = 1) # 위의 데이터셋에서 전체 인구수를 제외
pop_total['index'] = pop_total.index # 인덱스를 열로 생성
pop_total.T.drop(['index'], axis = 0) # 데이터셋 열->행, 행->열로 변환
Te = pop_total.T.drop(['index'], axis = 0) #인덱스 제거
Te
Te.index = ["10대 미만 남자", "10대 남자", "20대 남자", "30대 남자", "40대 남자", "50대 남자", "60대 남자", "70대 남자","80대 남자","90대 남자", "10대 미만 여자", "10대 여자", "20대 여자", "30대 여자", "40대 여자", "50대 여자", "60대 여자", "70대 여자","80대 여자","90대 여자"]
# 인덱스의 이름을 각 나이대별 성별로 지정
- 시각화에서 사용한 범례
- 인구수 1번 그래프 - 향남읍
Te_1 =pd.DataFrame(Te.sort_values(by = '41590259', ascending = False)['41590259']) #인구수가 가장 많은 향남읍의 인구 분포
plt.rcParams['font.size'] = 25 # 글씨 크기 설정
fig = plt.figure(figsize=(25,12)) # 캔버스 생성
circle_1 = plt.pie(Te_1['41590259'],labels = Te.index,autopct= '%1.1f%%', explode = [0.1,0.1,0.1,0.1,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]) # 중앙에서 떨어진 정도를 설정해서 인구 분포가 많은 나이대 강조
#plt.legend(loc = (1, 1)) # 원래 범례를 사용하였지만 하나의 범례를 사용하기 때문에 일단 그래프에서 범례 제거
plt.title('향남읍', size = 40) # 제목 크기
- 하위 1번 그래프 -신동
Te_4 =pd.DataFrame(Te.sort_values(by = '41590133', ascending = False)['41590133']) # 인구수가 가장 적은 신동의 인구 분포
plt.rcParams['font.size'] = 25 # 글씨 크기 설정
fig = plt.figure(figsize=(25,12)) # 캔버스 생성
circle_4 = plt.pie(Te_4['41590133'],labels = Te.index,autopct= '%1.1f%%', explode = [0.1,0.1,0.1,0.1,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]) # 중앙에서 떨어진 정도를 설정해서 인구 분포가 많은 나이대 강조
#plt.legend(loc = (1, 1)) # 원래 범례를 사용하였지만 하나의 범례를 사용하기 때문에 일단 그래프에서 범례 제거
plt.title('신동', size = 40) # 제목 크기
- 기본적으로 연령대 별 인구 분포에 따라 이용자 수가 나뉘는 것이 아님을 확인하고 신도시이기 때문에 영향을 받는다고 판단할 수 있다.
- emd 시각화
districts = emd_scco_geo[["id", "geometry"]].set_index("id") #id를 인덱스로 지정
districts.head() #5개의 데이터로 미리보기
plot_dict = emd_scco_geo.id.value_counts() # id값 추출
plot_dict.head() #5개의 데이터로 미리보기
emd_geo = new_emd_sort['geometry'] #geometry 정보 저장
new_emd_sort['pop_ttl'] = new_emd_sort['pop_ttl'].astype(int) # int형으로 변환
emd_select = new_emd_sort[['id', 'pop_ttl']] #new_emd_sort 에서 id와 인구수 뽑기
m = folium.Map(location=[36, 127], tiles="OpenStreetMap", zoom_start=7) # 기본 지도 설정
m.choropleth(
geo_data=emd_scco_geo, #geometry 정보를 담은 데이터셋을 설정
name='인구수',
data=emd_select, # 지도에 표시되는 데이터셋 지정
columns=['id', 'pop_ttl'], #id와 인구수 사용
key_on='feature.properties.id', #id별로 사용
color='grey',
fill_opacity=0.7,
line_opacity=0.3,
fill_color = 'PuRd' #크기별 구분 색깔
)
folium.LayerControl(collapsed=False).add_to(m) # 지도에 위에서 크기별로 색깔 변화준 것을 표시
m
- 인구 수가 중심구역과 동쪽 지역에 밀집되어 있다.
- 전체 버스정류장 시각화
for idx, row in station_table.iterrows():
folium.Marker([row['WGS84위도'], row['WGS84경도']]).add_to(m) # 위도 경도 표시
m.add_child(folium.LatLngPopup()) # 위도 경도 마커를 지도에 표시
emd_name_cd = pd.DataFrame(new_emd.loc[:,['emd_cd','emd_kor_nm']]) #읍면동 코드와 이름으로 새로운 데이터셋 생성
emd_name_cd = emd_name_cd.set_index('emd_cd') # 코드를 인덱스로 지정
emd_name_cd['index'] = emd_name_cd.index # 코드를 열로 생성
vis_new_data = pd.merge(pop_test, emd_name_cd, left_index = True,right_index = True) # pop_test, emd_name_cd 를 합치기
vis_new_data
- 읍면동별 인구 수 시각화
plt.figure(figsize = (18,4)) # 그림 크기 설정
plt.plot(vis_new_data['emd_kor_nm'],vis_new_data['pop_ttl']) # 읍면동 이름과 인구수로 그래프 그리기
- 분석을 진행하던 중 station_table 과 cell_pop 의 읍면동 이름이 다른 것을 확인
station_table['emd'].unique() # station_table 의 읍면동 이름
vis_new_data['emd_kor_nm'].unique() # vis_new_data 의 읍면동 이름
- 배양동(기안동), 청계동(동탄4동), 중동(동탄5동), 신동(동탄7동), 목동(동탄7동), 산척동(동탄7동), 장지동(동탄7동), 송동(동탄7동), 방교동(동탄6동), 금곡동(동탄6동), 남양읍(미포함)
- station_table 과 cell_pop에서 읍면동 이름이 다른 이유는 동탄으로 읍면동이 계속해서 변경되었기 때문에 발생한 문제이다. -> 크게 문제가 되지는 않다고 생각하여 그대로 진행하였다.
fig = px.scatter_mapbox(station_table, lat = 'WGS84위도', lon = 'WGS84경도',# 위도 경도 설정
color = 'emd', # 읍면동별로 색상 설정
opacity = 0.4, #투명도
hover_name = '정류소명', #정류소 명 표시
hover_data = {
'WGS84위도' : False,
'WGS84경도' : False,
'emd' : True # 읍면동 표시
},
mapbox_style = "carto-positron", # 기본 지도 설정
center = dict(lat = 37.15, lon = 126.9), # 지도 중심 설정
zoom = 9) # 확대 정도
fig.show() # 지도 보기
with open('C:\\Users\82108\Desktop\data mining\\tl_scco_emd.geojson', encoding='UTF-8') as f:
geo_data = json.load(f) # 지도 시각화에 사용하기 위해 geojson 으로 파일 오픈
geo_data
for x in geo_data['features']:
x['id'] = x['properties']['emd_kor_nm']
for idx, _ in enumerate(geo_data['features']):
print(geo_data['features'][idx]['id'])
# 지도 시각화를 위한 설정
emd_scco_geo['id_key'] = (emd_scco_geo.index)*10 #구분을 짓기 위해 index에 10을 곱한 id_key 열 생성
emd_scco_geo
fig2 = px.choropleth_mapbox(
emd_scco_geo,
geojson=geo_data,
locations='emd_kor_nm',
color='id_key',
color_continuous_scale=px.colors.sequential.Rainbow,
# featureidkey="properties.CTP_KOR_NM", # featureidkey를 사용하여 id 값을 갖는 키값 지정
mapbox_style="carto-positron",
zoom=9,
center = {"lat": 37.15, "lon": 126.9}
)
fig2.show()
fig.show()
fig3 = px.choropleth_mapbox(
new_emd,
geojson=geo_data,
locations='emd_kor_nm',
color='pop_ttl',
color_continuous_scale=px.colors.sequential.Blues,
# featureidkey="properties.CTP_KOR_NM", # featureidkey를 사용하여 id 값을 갖는 키값 지정
mapbox_style="carto-positron",
zoom=9,
center = {"lat": 37.15, "lon": 126.9}
)
fig3.show()
fig.show()
trans_2 = TripChain[TripChain["'||버스노선ID2||'"].notnull()] # 2번 환승
trans_3 = TripChain[TripChain["'||버스노선ID3||'"].notnull()] # 3번 환승
trans_4 = TripChain[TripChain["'||버스노선ID4||'"].notnull()] # 4번 환승
trans_5 = TripChain[TripChain["'||버스노선ID5||'"].notnull()] # 5번 환승
- 환승횟수별 시각화
sns.countplot(data = TripChain, x = "'||환승횟수||'")
- 1번 환승 외에 2-3번 환승을 주 타겟으로 함
TripChain[(TripChain["'||환승횟수||'"]==2) |(TripChain["'||환승횟수||'"]==3)] # 환승 횟수가 2,3 번인 행 추출
df_trip = pd.DataFrame(TripChain.groupby("'||승차역ID1||'")["암호화카드번호||'"].count()) # 승차역별 이용자 수
df_trip['이비카드정류장ID'] = df_trip.index #인덱스값을 이비카드정류장 ID로 설정
df_sta = station_table[["정류소명", "이비카드정류장ID", "emd","WGS84위도", "WGS84경도"]] # 사용할 열을 새로운 데이터셋에 저장
trip_station = pd.merge(df_sta, df_trip, how = 'inner', on = "이비카드정류장ID") # df_sta, df_trip 합치기
df_trip_3 = pd.DataFrame(TripChain.groupby("'||승차역ID2||'")["암호화카드번호||'"].count()) # 승차역2 별 이용자 수
df_trip_3['이비카드정류장ID'] = df_trip_3.index #인덱스값을 이비카드정류장 ID로 설정
trip_station_3 = pd.merge(df_sta, df_trip_3, how = 'inner', on = "이비카드정류장ID") # df_sta, df_trip3 합치기
trans_2= trip_station_3.sort_values("암호화카드번호||'",ascending = False).head(10) # 이용자 수가 많은 정류장 10개 추출
trans_2
df_trip_4 = pd.DataFrame(TripChain.groupby("'||승차역ID3||'")["암호화카드번호||'"].count()) # 승차역3 별 이용자 수
df_trip_4['이비카드정류장ID'] = df_trip_4.index #인덱스값을 이비카드정류장 ID로 설정
trip_station_4 = pd.merge(df_sta, df_trip_4, how = 'inner', on = "이비카드정류장ID") # df_sta, df_trip4 합치기
trans_1= trip_station_4.sort_values("암호화카드번호||'",ascending = False).head(10) # 이용자 수가 많은 정류장 10개 추출
trans_1
bus_top_15 = trip_station.sort_values("암호화카드번호||'",ascending = False).head(20) # 버스정류장 이용 탑 20
bus_top_40 = trip_station.sort_values("암호화카드번호||'",ascending = False).head(40) # 버스정류장 이용 탑 40
df_trip2 = pd.DataFrame(TripChain.groupby("'||최종하차역ID||'")["암호화카드번호||'"].count())
df_trip2 # 하차역 별 이용자 수
df_trip2['이비카드정류장ID'] = df_trip2.index # 인덱스 값을 열로 설정
trip_station_2 = pd.merge(df_sta, df_trip2, how = 'inner', on = "이비카드정류장ID") #df_sta, df_trip2 합치기 => 정류장 별 이용자 수
trip_station_2
bus_tail_15 = trip_station_2.sort_values("암호화카드번호||'",ascending = False).head(20) # 버스정류장 이용 하위 20
bus_tail_40 = trip_station_2.sort_values("암호화카드번호||'",ascending = False).head(40) # 버스정류장 이용 하위 40
bus_top_15
bus_tail_15.columns = ['정류소명', '이비카드정류장ID', 'emd' ,'WGS84위도' ,'WGS84경도', '이용자수']
bus_top_15.columns = ['정류소명', '이비카드정류장ID', 'emd' ,'WGS84위도' ,'WGS84경도', '이용자수']
bus_top_40.columns = ['정류소명', '이비카드정류장ID', 'emd' ,'WGS84위도' ,'WGS84경도', '이용자수']
bus_tail_40.columns = ['정류소명', '이비카드정류장ID', 'emd' ,'WGS84위도' ,'WGS84경도', '이용자수']
# 모든 테이블의 컬럼명을 변경
total_bus = pd.concat([bus_top_15, bus_tail_15], axis = 0) # 상위20개와 하위20개 데이터셋을 합치기
fig4 = px.scatter_mapbox(total_bus, lat = 'WGS84위도', lon = 'WGS84경도',
color = 'emd',
opacity = 0.4,
hover_name = '정류소명',
hover_data = {
'WGS84위도' : False,
'WGS84경도' : False,
'emd' : True
},
mapbox_style = "carto-positron",
center = dict(lat = 37.15, lon = 126.9),
zoom = 9)
fig4.show() # 상위, 하위 이용자 수의 버스정류장 시각화
bus_top_15.head(10) # 상위 10개
total_vis = bus_top_15.iloc[[0,1,2,3,4,5,7,9]] # 상위 하위에서 겹치는 정류장
for idx, row in total_vis.iterrows():
folium.Marker([row['WGS84위도'], row['WGS84경도']]).add_to(m)
m.add_child(folium.LatLngPopup()) #겹치는 정류장의 지도 시각화
-
4.1 최적 노선 제시
- 신영통현대타운.두산위브 + 신창미션힐.송화초교 + 한림대병원 + 메타폴리스 + 홈플러스.벌말초교 + 복합문화센터 + 동탄1동주민센터 + 삼성반도체후문으로 이용자 수가 많은 정류장을 거치는 노선을 신설한다. 기존 노선의 경우 위의 정류장으로 환승을 할 수 있도록 위의 중 한 정류장을 노선에 추가 하는 방향으로 노선을 개선할 수 있다.
-
4.2 활용 방안
-
- 이용자 수, 시간대에 맞춰 배차 간격 줄일 수 있다.
-
- 주요 정류장에 맞춘 새로운 노선을 신설한다.
-
- 청소년을 대상으로 진행하고 있는 사업에서 이용량이 적어 배차간격이 크고 배차되는 버스 차량의 수가 적은 동네에서 사용하는 교통비를 지원해주는 사업에 적용할 수 있다.
-